﻿using System;
using System.Collections;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace FRS.Devices
{
    public class SwitchBox : DeviceBase
    {
        private const int PORT = 2701;
        private const int OUTPUTS = 14;
        private readonly Queue commandQueue;

        public SwitchBox(Device device)
            : this(device.Address, device.Name)
        {
        }

        public SwitchBox(string address, string name) 
            : base(address, name)
        {
            commandQueue = Queue.Synchronized(new Queue());

            ToolStripMenuItem tsmi_230 = new ToolStripMenuItem("230V");
            RootMenuItem.DropDownItems.Add(tsmi_230);

            ToolStripMenuItem tsmi_24 = new ToolStripMenuItem("24V");
            RootMenuItem.DropDownItems.Add(tsmi_24);

            TreeNode node_230 = new TreeNode("230V");
            RootNode.Nodes.Add(node_230);

            TreeNode node_24 = new TreeNode("24V");
            RootNode.Nodes.Add(node_24);

            /* default output names */
            for (int i = 0; i < OUTPUTS; i++)
            {
                string output = "output " + i;
                OutputNames.Add(output);

                /* create the output treenodes */
                OutputTreeNode outputNode = new OutputTreeNode(this) { Text = output, OutputNr = i };
                if (i < 6)
                    node_230.Nodes.Add(outputNode);
                else
                    node_24.Nodes.Add(outputNode);
                
                OutputNodes.Add(outputNode);

                /*create the output menuitems*/
                OutputToolStripMenuItem outputMenuItem = new OutputToolStripMenuItem(i) { Text = output, CheckOnClick = true };
                outputMenuItem.Click += outputMenuItem_Click;
                if (i < 6)
                    tsmi_230.DropDownItems.Add(outputMenuItem);
                else
                    tsmi_24.DropDownItems.Add(outputMenuItem);

                OutputMenuItems.Add(outputMenuItem);
            }

            /* 230V */
            ToolStripSeparator tss_230 = new ToolStripSeparator();
            tsmi_230.DropDownItems.Add(tss_230);

            ToolStripMenuItem tsmi_230_on = new ToolStripMenuItem("all on");
            tsmi_230_on.Click += tsmi_230_on_Click;
            tsmi_230.DropDownItems.Add(tsmi_230_on);

            ToolStripMenuItem tsmi_230_off = new ToolStripMenuItem("all off");
            tsmi_230_off.Click += tsmi_230_off_Click;
            tsmi_230.DropDownItems.Add(tsmi_230_off);

            /* 24V */
            ToolStripSeparator tss_24 = new ToolStripSeparator();
            tsmi_24.DropDownItems.Add(tss_24);

            ToolStripMenuItem tsmi_24_on = new ToolStripMenuItem("all on");
            tsmi_24_on.Click += tsmi_24_on_Click;
            tsmi_24.DropDownItems.Add(tsmi_24_on);

            ToolStripMenuItem tsmi_24_off = new ToolStripMenuItem("all off");
            tsmi_24_off.Click += tsmi_24_off_Click;
            tsmi_24.DropDownItems.Add(tsmi_24_off);
        }

        void tsmi_230_on_Click(object sender, EventArgs e)
        {
            SetOutputs(0x3F, 0x3F);
        }

        void tsmi_230_off_Click(object sender, EventArgs e)
        {
            SetOutputs(0x3F, 0x00);
        }

        void tsmi_24_on_Click(object sender, EventArgs e)
        {
            SetOutputs(0x3FC0, 0x3FC0);
        }

        void tsmi_24_off_Click(object sender, EventArgs e)
        {
            SetOutputs(0x3FC0, 0x0000);
        }

        void outputMenuItem_Click(object sender, EventArgs e)
        {
            OutputToolStripMenuItem output = sender as OutputToolStripMenuItem;
            if (output != null) SetOutput(output.OutputNr, output.Checked ? 1 : 0);
        }

        protected override void Update()
        {
            try
            {
                if (UpdateNames)
                {
                    for (int j = 0; j < 14; j++)
                    {
                        /* commands in queue have a higher priority */
                        if (commandQueue.Count > 0) break;

                        StringBuilder name = new StringBuilder();
                        
                        /* get the characters 0-16 */
                        string hexString = base.TcpSendreceive(String.Format("!eer {0:d} 16\n", j * 32), PORT).Substring(0, 32);
                        for (int i = 0; i < hexString.Length; i += 2) name.Append(Char.ConvertFromUtf32(Convert.ToInt32(hexString.Substring(i, 2), 16)));
                        
                        /* characters 16-32 */
                        hexString = base.TcpSendreceive(String.Format("!eer {0:d} 16\n", j * 32 + 16), PORT).Substring(0, 32);
                        for (int i = 0; i < hexString.Length; i += 2) name.Append(Char.ConvertFromUtf32(Convert.ToInt32(hexString.Substring(i, 2), 16)));

                        OutputNames[j] = name.ToString();
                    }
                    UpdateNames = false;
                }

                if (UpdateValues)
                {
                    int port_230, port_24;
                    
                    string hexString = base.TcpSendreceive(String.Format("!io get port 2\n"), PORT);
                    port_230 = (int) Convert.ToUInt32(hexString.Substring(10, 2), 16);
                    hexString = base.TcpSendreceive(String.Format("!io get port 3\n"), PORT);
                    port_24 = (int)Convert.ToUInt32(hexString.Substring(10, 2), 16);

                    OutputValues = port_230 + (port_24 << 6);
                }

                /* set commands are higher prior than get values and get names */
                while(commandQueue.Count > 0)
                    base.TcpSendreceive(commandQueue.Dequeue() as String, PORT);
            }
            catch(Exception)
            {
                IsAlive = false;
                AliveStateChanged();
            }

            base.Update();
        }

        //private byte[] SB_TcpSendreceive(string command)
        //{
        //    if (command == null) return new byte[0];

        //    // Translate the passed message into ASCII and store it as a Byte array.
        //    Byte[] data = Encoding.ASCII.GetBytes(command);

        //    // Create a TcpClient.
        //    // Note, for this client to work you need to have a TcpServer 
        //    // connected to the same address as specified by the server, port
        //    // combination.
        //    TcpClient client = new TcpClientWithTimeout(Address, PORT, 2000).Connect();

        //    // Get a client stream for reading and writing.
        //    NetworkStream stream = client.GetStream();

        //    Thread.Sleep(25);

        //    // Send the message to the connected TcpServer. 
        //    stream.Write(data, 0, data.Length);

        //    // Receive the TcpServer.response.

        //    // Buffer to store the response bytes.
        //    data = new Byte[256];

        //    Thread.Sleep(25);

        //    // Read the first batch of the TcpServer response bytes.
        //    stream.Read(data, 0, data.Length);

        //    // Close everything.
        //    stream.Close();
        //    client.Close();

        //    /* if this all was successful, just set the alive state to alive */
        //    IsAlive = true;
        //    AliveStateChanged();

        //    Thread.Sleep(50);

        //    return data;
        //}

        public override void SetOutputName(int outputNr, string outputName)
        {
            if (outputName == null) return;

            StringBuilder hexString = new StringBuilder();
            if (outputName.Length > 32 * 2)
                outputName = outputName.Substring(0, 32 * 2);

            char[] values = outputName.ToCharArray();
            foreach (char letter in values)
            {
                // Get the integral value of the character.
                int value = Convert.ToInt32(letter);
                // Convert the decimal value to a hexadecimal value in string form.
                hexString.Append(String.Format("{0:X}", value));
            }

            /* fill rest of name with whitespace */
            while (hexString.Length <= 32*2) hexString.Append("20");

            commandQueue.Enqueue(String.Format("!eew {0:d} {1}\n", outputNr * 32, hexString.ToString().Substring(0, 32)));
            commandQueue.Enqueue(String.Format("!eew {0:d} {1}\n", outputNr * 32 + 16, hexString.ToString().Substring(32, 32)));

            base.SetOutputName(outputNr, outputName);
        }

        public override void SetOutput(int outputNr, int value)
        {
            int port = outputNr < 6 ? 2 : 3;
            int portPin = outputNr < 6 ? outputNr : outputNr - 6;
            
            /* send write outputs command to device */

            commandQueue.Enqueue(String.Format("!io set port {0:d} {1:x2} {2:x2}\n", port, (int)Math.Pow(2, portPin) * value, (int)Math.Pow(2, portPin)));
            
            base.SetOutput(outputNr, value);
        }

        public override void SetOutputs(int mask, int value)
        {
            SetOutputPorts(2, value & 0x3F, mask & 0x3F);

            SetOutputPorts(3, (value >> 6) & 0xFF, (mask >> 6) & 0xFF);

            base.SetOutputs(mask, value);
        }

        private void SetOutputPorts(int port,int value, int mask)
        {
            /* send write outputs command to device */
            commandQueue.Enqueue(String.Format("!io set port {2:d} {0:x2} {1:x2}\n", value, mask, port));
        }
    }
}
